/* eslint-disable no-use-before-define */
// FSM 可视化编辑器脚本
// 数据模型
const model = {
  states: [], // string[]
  events: [], // string[]
  initial: '',
  transitions: [], // { from, event, to }
  lifecycle: {}, // state -> { onEnter, onExit, onUpdate }
  selectedState: null,
  selectedEvent: null,
};

// 保存生命周期到模型，并刷新预览与本地存储
function saveLifecycleFor(stateId) {
  if (!stateId) {
    return;
  }
  const onEnter = (els.ctxLifeEnter?.value || '').trim();
  const onExit = (els.ctxLifeExit?.value || '').trim();
  const onUpdate = (els.ctxLifeUpdate?.value || '').trim();
  model.lifecycle[stateId] = { onEnter, onExit, onUpdate };
  if (els.tsPreview) {
    els.tsPreview.value = generateTS();
  }
  saveToStorage();
  if (els.ctxLifeStatus) {
    els.ctxLifeStatus.textContent = '已保存';
    setTimeout(() => {
      if (els.ctxLifeStatus) {
        els.ctxLifeStatus.textContent = '';
      }
    }, 1200);
  }
}

const STORAGE_KEY = 'fsm-editor-model-v1';

// 粘贴智能提取：从整段 TS/JS 中提取 config 对象
function stripComments(code) {
  // 移除块注释与行注释
  return code
    .replace(/\/\*[\s\S]*?\*\//g, '')
    .replace(/(^|[^:])\/\/.*$/gm, '$1');
}

// —— 自定义模态：打开“新增迁移” ——
function openAddTransModal(defaultFrom) {
  if (!els.addTransModal) {
    return;
  }
  // 小工具：根据是否选择“自定义事件”和输入内容，控制显隐/可用性/预览
  function updateTransPreview() {
    const from = els.transFrom?.value || '（未选）';
    const to = els.transTo?.value || '（未选）';
    const evSel = els.transEvent?.value || '';
    const evName =
      evSel === '__NEW__'
        ? (els.transNewEvent?.value || '').trim()
        : evSel || '（未选）';
    const pv = document.getElementById('trans-preview-text');
    if (pv) {
      pv.textContent = `${from} -- ${evName || '（未选）'} --> ${to}`;
    }
  }
  function updateTransModalOkState() {
    if (!els.btnAddTransOK) {
      return;
    }
    const isNew = els.transEvent && els.transEvent.value === '__NEW__';
    const name = (els.transNewEvent?.value || '').trim();
    els.btnAddTransOK.disabled = !!(isNew && !name);
  }
  function syncNewEventUI() {
    const isNew = els.transEvent && els.transEvent.value === '__NEW__';
    if (els.transNewEventWrap) {
      els.transNewEventWrap.style.display = isNew ? 'block' : 'none';
    }
    if (isNew && els.transNewEvent) {
      els.transNewEvent.focus();
      els.transNewEvent.select?.();
    }
    updateTransModalOkState();
    updateTransPreview();
  }
  // 绑定一次性处理（覆盖旧的）
  if (els.btnAddTransOK) {
    els.btnAddTransOK.onclick = () => {
      const from = els.transFrom?.value || '';
      let event = els.transEvent?.value || '';
      const to = els.transTo?.value || '';
      if (!from || !to || from === to) {
        els.addTransModal.style.display = 'none';
        return;
      }
      if (event === '__NEW__') {
        const custom = (els.transNewEvent?.value || '').trim();
        if (!custom) {
          els.addTransModal.style.display = 'none';
          return;
        }
        event = custom;
      }
      if (!model.events.includes(event)) {
        model.events.push(event);
      }
      const idx = model.transitions.findIndex(
        (t) => t.from === from && t.event === event
      );
      if (idx >= 0) {
        model.transitions[idx].to = to;
      } else {
        model.transitions.push({ from, event, to });
      }
      els.addTransModal.style.display = 'none';
      renderAll();
    };
  }
  if (els.btnAddTransCancel) {
    els.btnAddTransCancel.onclick = () => {
      els.addTransModal.style.display = 'none';
    };
  }
  // 源状态列表
  if (els.transFrom) {
    els.transFrom.innerHTML = '';
    model.states.forEach((s) => {
      const opt = document.createElement('option');
      opt.value = s;
      opt.textContent = s;
      if (defaultFrom && defaultFrom === s) {
        opt.selected = true;
      }
      els.transFrom.appendChild(opt);
    });
  }
  // 事件下拉 + 自定义项
  if (els.transEvent) {
    els.transEvent.innerHTML = '';
    model.events.forEach((ev) => {
      const opt = document.createElement('option');
      opt.value = ev;
      opt.textContent = ev;
      els.transEvent.appendChild(opt);
    });
    const optNew = document.createElement('option');
    optNew.value = '__NEW__';
    optNew.textContent = '自定义事件…';
    els.transEvent.appendChild(optNew);
    // 绑定 change 事件（使用 addEventListener 更稳健）
    els.transEvent.onchange = null;
    els.transEvent.addEventListener('change', syncNewEventUI);
    // 初始化一次（若默认就选在“自定义事件…”，确保输入框出现）
    if (model.events.length === 0) {
      els.transEvent.value = '__NEW__';
    }
    syncNewEventUI();
  }
  // 目标状态列表
  if (els.transTo) {
    els.transTo.innerHTML = '';
    model.states.forEach((s) => {
      const opt = document.createElement('option');
      opt.value = s;
      opt.textContent = s;
      els.transTo.appendChild(opt);
    });
  }
  // 初始化“新事件名”DOM与监听（可见性由 syncNewEventUI 决定）
  if (els.transNewEvent) {
    els.transNewEvent.value = '';
    els.transNewEvent.oninput = null;
    els.transNewEvent.addEventListener('input', () => {
      updateTransModalOkState();
      updateTransPreview();
    });
  }
  // 源/目标状态变化时更新预览
  els.transFrom?.addEventListener('change', updateTransPreview);
  els.transTo?.addEventListener('change', updateTransPreview);
  // 初始化一次按钮状态与预览
  updateTransModalOkState();
  updateTransPreview();
  // 展示弹窗后再同步一次，确保初始为“自定义事件…”时输入框可见并聚焦
  els.addTransModal.style.display = 'flex';
  setTimeout(() => {
    syncNewEventUI();
    if (
      els.transEvent &&
      els.transEvent.value === '__NEW__' &&
      els.transNewEvent
    ) {
      els.transNewEvent.focus();
      els.transNewEvent.select?.();
    }
  }, 0);
}

// —— 自定义模态：打开“新增状态” ——
function openAddStateModal() {
  if (!els.addStateModal || !els.addStateInput) {
    return;
  }
  if (els.btnAddStateOK) {
    els.btnAddStateOK.onclick = () => {
      const name = (els.addStateInput.value || '').trim();
      if (!name) {
        els.addStateModal.style.display = 'none';
        return;
      }
      if (model.states.includes(name)) {
        alert('已存在同名状态');
        return;
      }
      model.states.push(name);
      if (!model.initial) {
        model.initial = name;
      }
      els.addStateModal.style.display = 'none';
      renderAll();
    };
  }
  if (els.btnAddStateCancel) {
    els.btnAddStateCancel.onclick = () => {
      els.addStateModal.style.display = 'none';
    };
  }
  els.addStateInput.value = '';
  els.addStateModal.style.display = 'flex';
  setTimeout(() => {
    if (els.addStateInput) {
      els.addStateInput.focus();
    }
  }, 0);
}

// —— 自定义模态：打开“重命名状态” ——
function openRenameStateModal(oldName) {
  if (!els.renameStateModal || !els.renameStateInput) {
    return;
  }
  els.renameStateModal.dataset.oldName = oldName || '';
  els.renameStateInput.value = oldName || '';
  if (els.btnRenameStateOK) {
    els.btnRenameStateOK.onclick = () => {
      const prev = els.renameStateModal.dataset.oldName || '';
      const next = (els.renameStateInput.value || '').trim();
      if (!prev || !next || prev === next) {
        els.renameStateModal.style.display = 'none';
        return;
      }
      if (model.states.includes(next)) {
        alert('已存在同名状态');
        return;
      }
      model.states = model.states.map((x) => (x === prev ? next : x));
      model.transitions.forEach((t) => {
        if (t.from === prev) {
          t.from = next;
        }
        if (t.to === prev) {
          t.to = next;
        }
      });
      if (model.initial === prev) {
        model.initial = next;
      }
      if (model.lifecycle && model.lifecycle[prev]) {
        model.lifecycle[next] = model.lifecycle[prev];
        delete model.lifecycle[prev];
      }
      if (model.selectedState === prev) {
        model.selectedState = next;
      }
      els.renameStateModal.style.display = 'none';
      renderAll();
    };
  }
  if (els.btnRenameStateCancel) {
    els.btnRenameStateCancel.onclick = () => {
      els.renameStateModal.style.display = 'none';
    };
  }
  els.renameStateModal.style.display = 'flex';
  setTimeout(() => {
    if (els.renameStateInput) {
      els.renameStateInput.focus();
      // 选择文本方便直接输入
      try {
        els.renameStateInput.select();
      } catch (_) {}
    }
  }, 0);
}

// —— 自定义模态：打开“重命名事件（单条迁移）” ——
function openRenameEventModal(payload) {
  if (!els.renameEventModal || !els.renameEventInput) {
    return;
  }
  const { index, from, to, oldEvent } = payload || {};
  if (typeof index !== 'number') {
    return;
  }
  els.renameEventModal.dataset.index = String(index);
  els.renameEventModal.dataset.from = from || '';
  els.renameEventModal.dataset.to = to || '';
  els.renameEventModal.dataset.oldEvent = oldEvent || '';
  if (els.renameEventTip) {
    els.renameEventTip.textContent = `${from} → ${to}`;
  }
  els.renameEventInput.value = oldEvent || '';

  if (els.btnRenameEventOK) {
    els.btnRenameEventOK.onclick = () => {
      const idx = Number(els.renameEventModal.dataset.index || '-1');
      const prev = els.renameEventModal.dataset.oldEvent || '';
      const next = (els.renameEventInput.value || '').trim();
      if (idx < 0 || !next || next === prev) {
        els.renameEventModal.style.display = 'none';
        return;
      }
      // 修改该条迁移的事件名
      if (model.transitions[idx]) {
        model.transitions[idx].event = next;
      }
      if (!model.events.includes(next)) {
        model.events.push(next);
      }
      if (
        model.selectedEvent &&
        model.selectedEvent.event === prev &&
        model.selectedEvent.from === from &&
        model.selectedEvent.to === to
      ) {
        model.selectedEvent = { event: next, from, to };
      }
      els.renameEventModal.style.display = 'none';
      renderAll();
    };
  }
  if (els.btnRenameEventCancel) {
    els.btnRenameEventCancel.onclick = () => {
      els.renameEventModal.style.display = 'none';
    };
  }
  // 点击蒙层关闭
  els.renameEventModal.addEventListener('click', (ev) => {
    if (ev.target === els.renameEventModal) {
      els.renameEventModal.style.display = 'none';
    }
  });

  els.renameEventModal.style.display = 'flex';
  setTimeout(() => {
    if (els.renameEventInput) {
      els.renameEventInput.focus();
      try {
        els.renameEventInput.select();
      } catch (_) {}
    }
  }, 0);
}

function findObjectLiteralFrom(code, startIdx) {
  const n = code.length;
  let i = startIdx;
  while (i < n && code[i] !== '{') {
    i++;
  }
  if (i >= n) {
    return null;
  }
  let depth = 0;
  let inStr = false;
  let strCh = '';
  let escape = false;
  for (let j = i; j < n; j++) {
    const ch = code[j];
    if (inStr) {
      if (escape) {
        escape = false;
      } else if (ch === '\\') {
        escape = true;
      } else if (ch === strCh) {
        inStr = false;
        strCh = '';
      }
      continue;
    }
    if (ch === '"' || ch === "'" || ch === '`') {
      inStr = true;
      strCh = ch;
      continue;
    }
    if (ch === '{') {
      depth++;
    } else if (ch === '}') {
      depth--;
      if (depth === 0) {
        return code.slice(i, j + 1);
      }
    }
  }
  return null;
}

function tryExtractConfigObject(raw) {
  const code = String(raw);
  const noComments = stripComments(code);
  // 1) 匹配 const config = { ... }
  const re = /(export\s+)?(const|let|var)\s+config\s*[:=]/;
  const m = re.exec(noComments);
  if (m) {
    const start = m.index + m[0].length;
    const obj = findObjectLiteralFrom(noComments, start);
    if (obj) {
      try {
        const fn = new Function(`return (${obj})`);
        const data = fn();
        return data;
      } catch {
        /* noop */
      }
    }
  }
  // 2) 兜底：寻找包含 states 的首个对象字面量
  const firstBrace = noComments.indexOf('{');
  if (firstBrace !== -1 && /\bstates\b/.test(noComments)) {
    const obj = findObjectLiteralFrom(noComments, firstBrace);
    if (obj) {
      try {
        const fn = new Function(`return (${obj})`);
        const data = fn();
        return data;
      } catch (err) {
        /* noop */
      }
    }
  }
  return null;
}

// UI 元素
const els = {
  stateList: null,
  eventList: null,
  tsPreview: null,
  graph: null,
  onEnter: null,
  onExit: null,
  onUpdate: null,
  btnSaveLifecycle: null,
  btnAddState: null,
  btnAddEvent: null,
  btnAddTransition: null,
  btnSetInitial: null,
  btnExportTS: null,
  btnDownloadTS: null,
  btnReset: null,
  btnCopyTs: null,
  ctxMenu: null,
  ctxRename: null,
  ctxAddEvent: null,
  ctxAddTransition: null,
  ctxSetInitial: null,
  ctxDelete: null,
};

// Cytoscape 实例（全局）
let cy = null;
let ctxTargetState = null; // 右键菜单当前目标状态名
let ctxTargetEdge = null; // 右键菜单当前目标边 { index, from, to, event }

// 初始化
window.addEventListener('DOMContentLoaded', () => {
  bindEls();
  bindActions();
  loadInitialData();
  renderAll();
});

function bindEls() {
  els.stateList = document.getElementById('state-list');
  els.eventList = document.getElementById('event-list');
  els.tsPreview = document.getElementById('ts-preview');
  els.graph = document.getElementById('graph');
  els.onEnter = document.getElementById('onEnter');
  els.onExit = document.getElementById('onExit');
  els.onUpdate = document.getElementById('onUpdate');
  els.btnSaveLifecycle = document.getElementById('btn-save-lifecycle');
  els.btnDownloadTS = document.getElementById('btn-download-ts');
  els.btnReset = document.getElementById('btn-reset');
  els.btnCopyTs = document.getElementById('btn-copy-ts');
  els.btnExportJSON = document.getElementById('btn-export-json');
  els.btnImportJSON = document.getElementById('btn-import-json');
  els.jsonFile = document.getElementById('json-file');
  // 粘贴 Config 模态
  els.btnPasteConfig = document.getElementById('btn-paste-config');
  els.pasteModal = document.getElementById('paste-modal');
  els.pasteText = document.getElementById('paste-text');
  els.btnPasteImport = document.getElementById('btn-paste-import');
  els.btnPasteCancel = document.getElementById('btn-paste-cancel');
  els.addTransModal = document.getElementById('add-trans-modal');
  els.transFrom = document.getElementById('trans-from');
  els.transEvent = document.getElementById('trans-event');
  els.transNewEventWrap = document.getElementById('trans-new-event-wrap');
  els.transNewEvent = document.getElementById('trans-new-event');
  els.transTo = document.getElementById('trans-to');
  els.btnAddTransOK = document.getElementById('btn-add-trans-ok');
  els.btnAddTransCancel = document.getElementById('btn-add-trans-cancel');
  els.ctxMenu = document.getElementById('ctx-menu');
  els.ctxAddState = document.getElementById('ctx-add-state');
  els.ctxRename = document.getElementById('ctx-rename');
  els.ctxAddEvent = document.getElementById('ctx-add-event');
  els.ctxAddTransition = document.getElementById('ctx-add-transition');
  els.ctxSetInitial = document.getElementById('ctx-set-initial');
  els.ctxDelete = document.getElementById('ctx-delete');
  els.ctxLife = document.getElementById('ctx-life');
  els.ctxLifeEnter = document.getElementById('ctx-life-enter');
  els.ctxLifeExit = document.getElementById('ctx-life-exit');
  els.ctxLifeUpdate = document.getElementById('ctx-life-update');
  els.btnCtxLifeSave = document.getElementById('ctx-life-save');
  els.ctxLifeStatus = document.getElementById('ctx-life-status');
  // 边操作按钮
  els.ctxEdgeRename = document.getElementById('ctx-edge-rename');
  els.ctxEdgeReverse = document.getElementById('ctx-edge-reverse');
  els.ctxEdgeDelete = document.getElementById('ctx-edge-delete');

  // 新增/重命名状态模态
  els.addStateModal = document.getElementById('add-state-modal');
  els.addStateInput = document.getElementById('add-state-input');
  els.btnAddStateOK = document.getElementById('btn-add-state-ok');
  els.btnAddStateCancel = document.getElementById('btn-add-state-cancel');
  els.renameStateModal = document.getElementById('rename-state-modal');
  els.renameStateInput = document.getElementById('rename-state-input');
  els.btnRenameStateOK = document.getElementById('btn-rename-state-ok');
  els.btnRenameStateCancel = document.getElementById('btn-rename-state-cancel');
  // 重命名事件模态
  els.renameEventModal = document.getElementById('rename-event-modal');
  els.renameEventInput = document.getElementById('rename-event-input');
  els.renameEventTip = document.getElementById('rename-event-tip');
  els.btnRenameEventOK = document.getElementById('btn-rename-event-ok');
  els.btnRenameEventCancel = document.getElementById('btn-rename-event-cancel');
}

function bindActions() {
  els.btnDownloadTS.addEventListener('click', () => {
    const ts = generateTS();
    download('fsm-config.ts', ts);
  });

  // 导出 JSON
  if (els.btnExportJSON) {
    els.btnExportJSON.addEventListener('click', () => {
      // 导出为 MachineConfig JSON 结构
      const cfg = serializeConfig();
      download('fsm-config.json', JSON.stringify(cfg, null, 2));
    });
  }

  // 粘贴 Config：打开对话框
  if (els.btnPasteConfig && els.pasteModal) {
    els.btnPasteConfig.addEventListener('click', () => {
      if (els.pasteText) {
        els.pasteText.value = '';
      }
      els.pasteModal.style.display = 'flex';
    });
  }

  // 粘贴导入：解析 JSON 或 JS 对象字面量
  if (els.btnPasteImport && els.pasteText && els.pasteModal) {
    els.btnPasteImport.addEventListener('click', () => {
      const raw = els.pasteText.value.trim();
      if (!raw) {
        els.pasteModal.style.display = 'none';
        return;
      }
      // 先尝试“智能提取”，以支持整段 TS 文件/代码片段
      let data = tryExtractConfigObject(raw);
      if (!data) {
        // 回退：尝试 JSON.parse
        try {
          data = JSON.parse(raw);
        } catch {
          // 再回退：当做对象字面量求值（允许函数存在）
          try {
            const fn = new Function(`return (${raw})`);
            data = fn();
          } catch {
            data = null;
          }
        }
      }
      if (!data || !isConfigLike(data)) {
        alert('解析失败：请粘贴合法的 MachineConfig 对象或 JSON');
        return;
      }
      applyConfig(data);
      saveToStorage();
      els.pasteModal.style.display = 'none';
      renderAll();
    });
  }

  // 取消粘贴
  if (els.btnPasteCancel && els.pasteModal) {
    els.btnPasteCancel.addEventListener('click', () => {
      els.pasteModal.style.display = 'none';
    });
  }

  // 导入 JSON
  if (els.btnImportJSON && els.jsonFile) {
    els.btnImportJSON.addEventListener('click', () => {
      els.jsonFile.value = '';
      els.jsonFile.click();
    });
    els.jsonFile.addEventListener('change', async () => {
      const file = els.jsonFile.files && els.jsonFile.files[0];
      if (!file) {
        return;
      }
      try {
        const text = await file.text();
        const data = JSON.parse(text);
        if (isConfigLike(data)) {
          applyConfig(data);
        } else if (validateModel(data)) {
          // 兼容旧版导出的内部模型 JSON
          applyModel(data);
        } else {
          alert('导入失败：JSON 结构不合法');
          return;
        }
        saveToStorage();
        renderAll();
      } catch {
        alert('导入失败：无法解析 JSON');
      }
    });
  }

  if (els.btnCopyTs) {
    els.btnCopyTs.addEventListener('click', async () => {
      // 确保预览为最新
      const ts = generateTS();
      els.tsPreview.value = ts;
      try {
        if (navigator.clipboard && window.isSecureContext) {
          await navigator.clipboard.writeText(ts);
        } else {
          // 兼容不支持 Clipboard API 的环境
          const ta = document.createElement('textarea');
          ta.value = ts;
          ta.style.position = 'fixed';
          ta.style.opacity = '0';
          document.body.appendChild(ta);
          ta.focus();
          ta.select();
          document.execCommand('copy');
          document.body.removeChild(ta);
        }
        const old = els.btnCopyTs.textContent;
        els.btnCopyTs.textContent = '已复制';
        setTimeout(() => (els.btnCopyTs.textContent = old), 1200);
      } catch (e) {
        alert('复制失败，请手动复制');
      }
    });
  }

  els.btnReset.addEventListener('click', () => {
    if (!confirm('确定清空当前编辑内容？')) {
      return;
    }
    model.states = [];
    model.events = [];
    model.initial = '';
    model.transitions = [];
    model.lifecycle = {};
    model.selectedState = null;
    renderAll();
  });

  if (els.btnSaveLifecycle) {
    els.btnSaveLifecycle.addEventListener('click', () => {
      const s = model.selectedState;
      if (!s) {
        return alert('请先选择状态');
      }
      model.lifecycle[s] = {
        onEnter: (els.onEnter?.value || '').trim(),
        onExit: (els.onExit?.value || '').trim(),
        onUpdate: (els.onUpdate?.value || '').trim(),
      };
      renderAll();
    });
  }

  // 右键菜单行为：全局点击/滚动/窗口变化时关闭，绑定菜单项（只绑定一次）
  if (els.ctxMenu) {
    document.addEventListener('mousedown', (ev) => {
      // 点击非菜单区域关闭
      if (els.ctxMenu && !els.ctxMenu.contains(ev.target)) {
        closeCtxMenu();
      }
    });
    // 仅当滚动发生在菜单外部时，才关闭菜单；避免在生命周期文本域滚动时被误关
    window.addEventListener(
      'scroll',
      (ev) => {
        const t = ev.target;
        if (els.ctxMenu && t && els.ctxMenu.contains(t)) {
          return;
        }
        closeCtxMenu();
      },
      true
    );
    window.addEventListener('resize', () => closeCtxMenu());
    // 在菜单内部交互不关闭，也不让滚轮、触摸等事件传到 cytoscape 触发 pan/zoom
    [
      'click',
      'mousedown',
      'mouseup',
      'pointerdown',
      'pointerup',
      'touchstart',
      'touchmove',
    ].forEach((type) => {
      els.ctxMenu.addEventListener(type, (e) => e.stopPropagation());
    });
    els.ctxMenu.addEventListener(
      'wheel',
      (e) => {
        e.stopPropagation();
      },
      { passive: true }
    );

    // 绑定菜单项（节点相关）
    els.ctxAddState?.addEventListener('click', () => {
      openAddStateModal();
    });

    els.ctxRename?.addEventListener('click', () => {
      if (!ctxTargetState) {
        return;
      }
      openRenameStateModal(ctxTargetState);
    });

    // 已移除“新增事件”功能，避免与“新增迁移”里的自定义事件重复
    if (els.ctxAddEvent) {
      els.ctxAddEvent.style.display = 'none';
    }

    els.ctxAddTransition?.addEventListener('click', () => {
      openAddTransModal(
        ctxTargetState || (model.selectedState ?? model.states[0] ?? '')
      );
    });

    els.ctxSetInitial?.addEventListener('click', () => {
      if (!ctxTargetState) {
        return;
      }
      model.initial = ctxTargetState;
      closeCtxMenu();
      renderAll();
    });

    els.ctxDelete?.addEventListener('click', () => {
      if (!ctxTargetState) {
        return;
      }
      const name = ctxTargetState;
      if (!confirm(`删除状态 ${name}？关联的迁移也会被删除`)) {
        return;
      }
      model.states = model.states.filter((x) => x !== name);
      model.transitions = model.transitions.filter(
        (t) => t.from !== name && t.to !== name
      );
      if (model.initial === name) {
        model.initial = model.states[0] || '';
      }
      delete model.lifecycle[name];
      if (model.selectedState === name) {
        model.selectedState = null;
      }
      closeCtxMenu();
      renderAll();
    });

    // 生命周期保存
    els.btnCtxLifeSave?.addEventListener('click', () => {
      if (!ctxTargetState) {
        return;
      }
      saveLifecycleFor(ctxTargetState);
    });

    els.ctxEdgeRename?.addEventListener('click', () => {
      const { index, from, to, event } = ctxTargetEdge;
      openRenameEventModal({ index, from, to, oldEvent: event });
      closeCtxMenu();
    });
    els.ctxEdgeReverse?.addEventListener('click', () => {
      if (!ctxTargetEdge) {
        return;
      }
      const { index, from, to, event } = ctxTargetEdge;
      if (from === to) {
        return; // 自循环已被禁止
      }
      const t = model.transitions[index];
      if (!t) {
        return;
      }
      // 反转方向
      t.from = to;
      t.to = from;
      closeCtxMenu();
      renderAll();
    });
    els.ctxEdgeDelete?.addEventListener('click', () => {
      if (!ctxTargetEdge) {
        return;
      }
      const { index, from, to, event } = ctxTargetEdge;
      if (!confirm(`删除迁移：${event} (${from} -> ${to})？`)) {
        return;
      }
      if (index >= 0 && index < model.transitions.length) {
        model.transitions.splice(index, 1);
      }
      if (
        model.selectedEvent &&
        model.selectedEvent.event === event &&
        model.selectedEvent.from === from &&
        model.selectedEvent.to === to
      ) {
        model.selectedEvent = null;
      }
      closeCtxMenu();
      renderAll();
    });
  }

  // 全局键盘快捷键：Esc 关闭打开的模态；Enter 提交
  window.addEventListener('keydown', (e) => {
    const isTextArea = e.target && e.target.tagName === 'TEXTAREA';
    if (e.key === 'Escape') {
      if (els.addTransModal && els.addTransModal.style.display !== 'none') {
        els.addTransModal.style.display = 'none';
      }
      if (els.addStateModal && els.addStateModal.style.display !== 'none') {
        els.addStateModal.style.display = 'none';
      }
      if (
        els.renameStateModal &&
        els.renameStateModal.style.display !== 'none'
      ) {
        els.renameStateModal.style.display = 'none';
      }
      if (
        els.renameEventModal &&
        els.renameEventModal.style.display !== 'none'
      ) {
        els.renameEventModal.style.display = 'none';
      }
    } else if (e.key === 'Enter' && !isTextArea) {
      if (els.addTransModal && els.addTransModal.style.display !== 'none') {
        els.btnAddTransOK?.click();
      } else if (
        els.addStateModal &&
        els.addStateModal.style.display !== 'none'
      ) {
        els.btnAddStateOK?.click();
      } else if (
        els.renameStateModal &&
        els.renameStateModal.style.display !== 'none'
      ) {
        els.btnRenameStateOK?.click();
      } else if (
        els.renameEventModal &&
        els.renameEventModal.style.display !== 'none'
      ) {
        els.btnRenameEventOK?.click();
      }
    }
  });
}

function loadSampleFromAppTs() {
  // 以当前 server/src/App.ts 里的示例为初始数据
  model.states = ['idle', 'loading', 'success', 'failure'];
  model.events = ['FETCH', 'RESOLVE', 'REJECT'];
  model.initial = 'idle';
  model.transitions = [
    { from: 'idle', event: 'FETCH', to: 'loading' },
    { from: 'loading', event: 'RESOLVE', to: 'success' },
    { from: 'loading', event: 'REJECT', to: 'failure' },
  ];
  model.lifecycle = {
    idle: {
      onEnter:
        "(_, motion) => {\n  console.log('[onEnter] 初始化，切换到 idle 状态');\n  motion.loadByName('启动').play();\n}",
      onExit: "() => {\n  console.log('[onExit] 离开 idle 状态');\n}",
      onUpdate: '',
    },
    loading: {
      onEnter:
        "(_, motion) => {\n  console.log('[onEnter] 切换到动画到 loading 状态');\n  motion.loadByName([{ name: '旋转', iterations: Infinity }]).play();\n}",
      onExit: "() => {\n  console.log('[onExit] 离开 loading 状态');\n}",
      onUpdate: '',
    },
    success: { onEnter: '', onExit: '', onUpdate: '' },
    failure: { onEnter: '', onExit: '', onUpdate: '' },
  };
}

// ---------- 持久化：本地存储 + 导入/导出 ----------
function loadInitialData() {
  const data = loadFromStorage();
  if (data && validateModel(data)) {
    applyModel(data);
  } else {
    loadSampleFromAppTs();
  }
}

function serializeModel() {
  return {
    states: [...model.states],
    events: [...model.events],
    initial: model.initial,
    transitions: model.transitions.map((t) => ({
      from: t.from,
      event: t.event,
      to: t.to,
    })),
    lifecycle: { ...model.lifecycle },
  };
}

function applyModel(data) {
  model.states = Array.isArray(data.states) ? [...data.states] : [];
  model.events = Array.isArray(data.events) ? [...data.events] : [];
  model.initial = typeof data.initial === 'string' ? data.initial : '';
  model.transitions = Array.isArray(data.transitions)
    ? data.transitions.map((t) => ({ from: t.from, event: t.event, to: t.to }))
    : [];
  model.lifecycle =
    typeof data.lifecycle === 'object' && data.lifecycle
      ? { ...data.lifecycle }
      : {};
  // 清理选中
  model.selectedState = null;
  model.selectedEvent = null;
  // 规范化：去重同一状态下相同事件的重复迁移，保留最后一次
  dedupeTransitions();
}

function validateModel(data) {
  try {
    if (!data || typeof data !== 'object') {
      return false;
    }
    if (
      !Array.isArray(data.states) ||
      !Array.isArray(data.events) ||
      !Array.isArray(data.transitions)
    ) {
      return false;
    }
    if (typeof data.initial !== 'string') {
      return false;
    }
    for (const t of data.transitions) {
      if (
        !t ||
        typeof t !== 'object' ||
        typeof t.from !== 'string' ||
        typeof t.event !== 'string' ||
        typeof t.to !== 'string'
      ) {
        return false;
      }
    }
    return true;
  } catch {
    return false;
  }
}

function saveToStorage() {
  try {
    const data = serializeModel();
    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
  } catch {
    // ignore
  }
}

function loadFromStorage() {
  try {
    const text = localStorage.getItem(STORAGE_KEY);
    if (!text) {
      return null;
    }
    return JSON.parse(text);
  } catch {
    return null;
  }
}

// 确保每个 (from, event) 只存在一个迁移，若有重复则保留最后一次
function dedupeTransitions() {
  const map = new Map();
  for (const t of model.transitions) {
    // 跳过自循环
    if (!t || t.from === t.to) {
      continue;
    }
    const key = `${t.from}@@${t.event}`;
    map.set(key, { from: t.from, event: t.event, to: t.to });
  }
  model.transitions = Array.from(map.values());
}

// 移除未被任何迁移引用的事件，保持事件列表精简
function cleanUnusedEvents() {
  const used = new Set(model.transitions.map((t) => t.event));
  const before = model.events.length;
  model.events = model.events.filter((e) => used.has(e));
  return before !== model.events.length;
}

// ---------- MachineConfig JSON 转换 ----------
function isConfigLike(data) {
  return (
    data &&
    typeof data === 'object' &&
    typeof data.initial === 'string' &&
    data.states &&
    typeof data.states === 'object'
  );
}

function serializeConfig() {
  // 根据当前 model 生成 MachineConfig JSON
  const cfg = { initial: model.initial || '', states: {} };
  // 聚合 on 映射
  const onMap = {};
  model.states.forEach((s) => (onMap[s] = {}));
  model.transitions.forEach(({ from, event, to }) => {
    if (!onMap[from]) {
      onMap[from] = {};
    }
    onMap[from][event] = to;
  });
  // 组装状态
  model.states.forEach((s) => {
    const life = model.lifecycle[s] || {};
    const stateCfg = {};
    if (Object.keys(onMap[s] || {}).length) {
      stateCfg.on = onMap[s];
    }
    if (life.onEnter) {
      stateCfg.onEnter = life.onEnter;
    }
    if (life.onExit) {
      stateCfg.onExit = life.onExit;
    }
    if (life.onUpdate) {
      stateCfg.onUpdate = life.onUpdate;
    }
    cfg.states[s] = stateCfg;
  });
  return cfg;
}

function applyConfig(cfg) {
  // 从 MachineConfig JSON 构建内部 model
  const states = Object.keys(cfg.states || {});
  const events = new Set();
  const transitions = [];
  const lifecycle = {};
  states.forEach((s) => {
    const def = cfg.states[s] || {};
    const on = def.on || {};
    Object.keys(on).forEach((ev) => {
      events.add(ev);
      const to = on[ev];
      if (to && to !== s) {
        transitions.push({ from: s, event: ev, to });
      }
    });
    lifecycle[s] = {
      onEnter:
        typeof def.onEnter === 'string'
          ? def.onEnter
          : typeof def.onEnter === 'function'
            ? def.onEnter.toString()
            : '',
      onExit:
        typeof def.onExit === 'string'
          ? def.onExit
          : typeof def.onExit === 'function'
            ? def.onExit.toString()
            : '',
      onUpdate:
        typeof def.onUpdate === 'string'
          ? def.onUpdate
          : typeof def.onUpdate === 'function'
            ? def.onUpdate.toString()
            : '',
    };
  });
  model.states = states;
  model.events = Array.from(events);
  model.initial = typeof cfg.initial === 'string' ? cfg.initial : '';
  model.transitions = transitions;
  model.lifecycle = lifecycle;
  model.selectedState = null;
  model.selectedEvent = null;
}

function renderAll() {
  // 渲染前做一次去重，防止历史数据或导入产生重复边
  dedupeTransitions();
  // 同步清理未使用事件
  cleanUnusedEvents();
  renderStateList();
  renderEventList();
  renderGraph();
  els.tsPreview.value = generateTS();
  renderLifecycleEditor();
  // 自动保存
  saveToStorage();
}

function renderStateList() {
  els.stateList.innerHTML = '';
  model.states.forEach((s) => {
    const li = document.createElement('li');
    if (model.selectedState === s) {
      li.classList.add('active');
    }
    const isInitial = model.initial === s;
    li.innerHTML = `
      <span class="item-title">
        <span class="state-name">${s}</span>
        ${isInitial ? '<span class="badge badge-initial">initial</span>' : ''}
      </span>
      <span class="item-actions">
        <button data-act="select">选中</button>
        <button data-act="rename">重命名</button>
        <button data-act="delete">删除</button>
      </span>
    `;
    li.querySelector('[data-act="select"]').addEventListener('click', () => {
      model.selectedState = s;
      // 互斥：清除事件选中
      model.selectedEvent = null;
      // 更新列表高亮
      renderStateList();
      renderEventList();
      // 在图上选中并居中此节点
      selectStateInGraph(s, true);
    });
    li.querySelector('[data-act="rename"]').addEventListener('click', () => {
      openRenameStateModal(s);
    });
    li.querySelector('[data-act="delete"]').addEventListener('click', () => {
      if (!confirm(`删除状态 ${s}？关联的迁移也会被删除`)) {
        return;
      }
      model.states = model.states.filter((x) => x !== s);
      model.transitions = model.transitions.filter(
        (t) => t.from !== s && t.to !== s
      );
      if (model.initial === s) {
        model.initial = model.states[0] || '';
      }
      delete model.lifecycle[s];
      if (model.selectedState === s) {
        model.selectedState = null;
      }
      renderAll();
    });
    els.stateList.appendChild(li);
  });
}

function renderEventList() {
  els.eventList.innerHTML = '';
  // 列出每一条迁移（允许同名事件重复显示）
  model.transitions.forEach((t, idx) => {
    const { from, event, to } = t;
    const li = document.createElement('li');
    // 选中态：根据精确匹配
    if (
      model.selectedEvent &&
      model.selectedEvent.event === event &&
      model.selectedEvent.from === from &&
      model.selectedEvent.to === to
    ) {
      li.classList.add('active');
    }
    li.innerHTML = `
      <span class="item-title">
        <span class="event-name">${event}</span>
        <span class="meta">${from} → ${to}</span>
      </span>
      <span class="item-actions">
        <button data-act="select">选中</button>
        <button data-act="rename">重命名</button>
        <button data-act="delete">删除</button>
      </span>
    `;
    li.querySelector('[data-act="select"]').addEventListener('click', () => {
      // 互斥：清除状态选中
      model.selectedState = null;
      model.selectedEvent = { event, from, to };
      renderEventList();
      renderStateList();
      // 在图上高亮并对准该边
      selectTransitionInGraph(from, to, event, true);
    });
    li.querySelector('[data-act="rename"]').addEventListener('click', () => {
      openRenameEventModal({ index: idx, from, to, oldEvent: event });
    });
    li.querySelector('[data-act="delete"]').addEventListener('click', () => {
      if (!confirm(`删除迁移：${event} (${from} -> ${to})？`)) {
        return;
      }
      // 仅删除这一条迁移
      model.transitions.splice(idx, 1);
      // 若选中项即为此迁移，取消选中
      if (
        model.selectedEvent &&
        model.selectedEvent.event === event &&
        model.selectedEvent.from === from &&
        model.selectedEvent.to === to
      ) {
        model.selectedEvent = null;
      }
      renderAll();
    });
    els.eventList.appendChild(li);
  });
}

function renderLifecycleEditor() {
  // 已移除生命周期编辑器，此函数保留为空以兼容现有调用
}

// 在 Cytoscape 图上选中并居中某个状态节点
function selectStateInGraph(stateId, center = false) {
  if (!cy) {
    return;
  }
  const node = cy.getElementById(stateId);
  if (!node || node.empty()) {
    return;
  }
  // 取消其它选择，仅选中该节点
  cy.batch(() => {
    cy.$('node:selected').unselect();
    node.select();
  });
  if (center) {
    cy.center(node);
  }
}

// 在图上选中并视图对准某事件的所有边
function selectEventInGraph(eventName, center = false) {
  if (!cy) {
    return;
  }
  const edges = cy.edges().filter((e) => e.data('label') === eventName);
  if (!edges || edges.empty()) {
    return;
  }
  cy.batch(() => {
    cy.$('edge:selected').unselect();
    edges.select();
  });
  if (center) {
    cy.fit(edges, 40);
  }
}

// 精确选中某条迁移边（from,event,to）
function selectTransitionInGraph(from, to, eventName, center = false) {
  if (!cy) {
    return;
  }
  const edges = cy.edges().filter((e) => {
    const d = e.data();
    return d.source === from && d.target === to && d.label === eventName;
  });
  if (!edges || edges.empty()) {
    return;
  }
  cy.batch(() => {
    cy.$('edge:selected').unselect();
    edges.select();
  });
  if (center) {
    cy.fit(edges, 40);
  }
}

function renderGraph() {
  // 初始化 cytoscape（首次）
  const container = document.getElementById('graph');
  if (!cy) {
    cy = cytoscape({
      container,
      style: [
        {
          selector: 'node',
          style: {
            'background-color': '#fff',
            'border-color': '#94a3b8',
            'border-width': 1.5,
            shape: 'round-rectangle',
            padding: '8px',
            width: 'label',
            height: 'label',
            label: 'data(label)',
            'text-valign': 'center',
            'text-halign': 'center',
            'font-size': 12,
            'text-wrap': 'wrap',
            'text-max-width': '160px',
          },
        },
        {
          selector: 'node.initial',
          style: {
            'border-color': '#2563eb',
            'border-width': 2,
          },
        },
        {
          selector: 'edge',
          style: {
            width: 1.5,
            'line-color': '#94a3b8',
            'target-arrow-color': '#94a3b8',
            'target-arrow-shape': 'triangle',
            label: 'data(label)',
            'font-size': 11,
            'curve-style': 'bezier',
            'text-rotation': 'autorotate',
            'text-margin-y': -6,
            'text-background-color': '#ffffff',
            'text-background-opacity': 0.8,
            'text-background-padding': '2px',
          },
        },
        {
          selector: 'edge:selected',
          style: {
            width: 3,
            'line-color': '#f59e0b',
            'target-arrow-color': '#f59e0b',
          },
        },
        {
          selector: 'node:selected',
          style: {
            'border-color': '#f59e0b',
            'border-width': 3,
          },
        },
      ],
      wheelSensitivity: 0.2,
      minZoom: 0.1,
      maxZoom: 2,
    });

    // 点击节点选中并同步到生命周期编辑器
    cy.on('tap', 'node', (evt) => {
      const id = evt.target.id();
      model.selectedState = id;
      // 互斥：清除事件选中
      model.selectedEvent = null;
      // 同步左侧列表高亮
      renderStateList();
      renderEventList();
    });

    // 点击边：高亮该边并在事件列表中高亮对应的“迁移卡片”
    cy.on('tap', 'edge', (evt) => {
      const edge = evt.target;
      const d = edge.data();
      const eventName = d.label;
      const from = d.source;
      const to = d.target;
      model.selectedEvent = eventName ? { event: eventName, from, to } : null;
      // 互斥：清除状态选中
      model.selectedState = null;
      renderEventList();
      renderStateList();
      cy.batch(() => {
        cy.$('edge:selected').unselect();
        edge.select();
      });
      // 不在画布点击时自动居中，避免打断操作。
      // 居中只在事件列表操作时触发（selectTransitionInGraph 中处理）。
    });

    // 禁用浏览器默认右键菜单（在图区域）
    container.addEventListener('contextmenu', (e) => {
      e.preventDefault();
    });

    // 右键节点：打开自定义菜单
    cy.on('cxttap', 'node', (evt) => {
      const id = evt.target.id();
      model.selectedState = id;
      // 互斥：清除事件选中
      model.selectedEvent = null;
      // 这是节点菜单，清空边上下文
      ctxTargetEdge = null;
      // 同步左侧列表高亮
      renderStateList();
      renderEventList();
      // 在图上选中该节点
      const node = evt.target;
      cy.batch(() => {
        cy.$('node:selected').unselect();
        node.select();
      });
      const oe = evt.originalEvent;
      if (
        oe &&
        typeof oe.clientX === 'number' &&
        typeof oe.clientY === 'number'
      ) {
        openCtxMenu(oe.clientX, oe.clientY, id);
      }
    });

    // 交互时关闭菜单
    cy.on('pan zoom dragfree', () => closeCtxMenu());
    // 点击画布空白处关闭
    cy.on('tap', (evt) => {
      if (evt.target === cy) {
        closeCtxMenu();
      }
    });
    // 右键空白处：打开仅包含“新增状态”的菜单
    cy.on('cxttap', (evt) => {
      if (evt.target === cy) {
        const oe = evt.originalEvent;
        if (
          oe &&
          typeof oe.clientX === 'number' &&
          typeof oe.clientY === 'number'
        ) {
          // 清除选中，展示空白菜单
          ctxTargetState = null;
          ctxTargetEdge = null;
          model.selectedState = null;
          renderStateList();
          renderEventList();
          openCtxMenu(oe.clientX, oe.clientY, null);
        }
      }
    });

    // 右键边：打开“边操作”菜单
    cy.on('cxttap', 'edge', (evt) => {
      const edge = evt.target;
      const d = edge.data();
      const eventName = d.label;
      const from = d.source;
      const to = d.target;
      const idx = model.transitions.findIndex(
        (t) => t.from === from && t.to === to && t.event === eventName
      );
      // 设置选中迁移，清除状态选中
      model.selectedEvent = eventName ? { event: eventName, from, to } : null;
      model.selectedState = null;
      renderEventList();
      renderStateList();
      // 图上选中这条边
      cy.batch(() => {
        cy.$('edge:selected').unselect();
        edge.select();
      });
      ctxTargetState = null;
      ctxTargetEdge = { index: idx, from, to, event: eventName };
      const oe = evt.originalEvent;
      if (
        oe &&
        typeof oe.clientX === 'number' &&
        typeof oe.clientY === 'number'
      ) {
        openCtxMenu(oe.clientX, oe.clientY, null);
      }
    });
  }

  // 记录已有位置，避免每次都重排
  const posMap = {};
  if (cy) {
    cy.nodes().forEach((n) => (posMap[n.id()] = { ...n.position() }));
  }

  // 生成元素集合
  const elements = [];
  model.states.forEach((s) => {
    const label = s; // 不在标签中追加 (initial)，用样式标识初始状态
    elements.push({
      data: { id: s, label },
      classes: model.initial === s ? 'initial' : '',
    });
  });
  model.transitions.forEach(({ from, event, to }, idx) => {
    elements.push({
      data: {
        id: `e${idx}-${from}-${to}-${event}`,
        source: from,
        target: to,
        label: event,
      },
    });
  });

  cy.elements().remove();
  cy.add(elements);

  // 恢复/布局
  const hasPositions = Object.keys(posMap).length > 0;
  model.states.forEach((s) => {
    if (posMap[s]) {
      const node = cy.getElementById(s);
      node.position(posMap[s]);
    }
  });

  if (!hasPositions) {
    cy.layout({
      name: 'dagre',
      rankDir: 'LR',
      nodeSep: 30,
      rankSep: 40,
      spacingFactor: 1.1,
    }).run();
    cy.fit(undefined, 20);
  }
  // 确保画布尺寸与容器同步，防止滚动条异常增长
  cy.resize();
  if (hasPositions) {
    // 若已有位置但元素变化（新增/删除），适度调整视图但不打乱手动布局
    const padding = 20;
    cy.fit(undefined, padding);
  }
  // 若当前有选中的状态，渲染后在图上选中它
  if (model.selectedState) {
    selectStateInGraph(model.selectedState, false);
  }
  // 若当前有选中的事件/迁移，渲染后在图上选中相应的边
  if (model.selectedEvent) {
    const se = model.selectedEvent;
    if (typeof se === 'string') {
      selectEventInGraph(se, false);
    } else if (se && typeof se === 'object') {
      selectTransitionInGraph(se.from, se.to, se.event, false);
    }
  }
}

function openCtxMenu(clientX, clientY, stateId) {
  if (!els.ctxMenu) {
    return;
  }
  ctxTargetState = stateId;
  if (stateId) {
    // 打开的是节点菜单，确保不显示边操作项
    ctxTargetEdge = null;
  }
  // 简单防止越界
  const menu = els.ctxMenu;
  const rect = menu.getBoundingClientRect();
  const pad = 8;
  const maxX = window.innerWidth - (rect.width || 180) - pad;
  const maxY = window.innerHeight - (rect.height || 160) - pad;
  const x = Math.min(clientX, maxX);
  const y = Math.min(clientY, maxY);
  menu.style.left = `${x}px`;
  menu.style.top = `${y}px`;
  updateCtxMenu(stateId);
  menu.style.display = 'block';
}

function closeCtxMenu() {
  if (els.ctxMenu) {
    els.ctxMenu.style.display = 'none';
  }
  // 离开菜单时尝试保存当前节点的生命周期编辑
  if (ctxTargetState) {
    saveLifecycleFor(ctxTargetState);
  }
  ctxTargetState = null;
}

// 根据是否有 stateId 来更新菜单显示项与生命周期示例
function updateCtxMenu(stateId) {
  // 控制节点相关操作的可见性
  const nodeOps = [
    els.ctxRename,
    els.ctxAddTransition,
    els.ctxSetInitial,
    els.ctxDelete,
    els.ctxLife,
  ];
  const isNode = !!stateId;
  const isEdge = !!ctxTargetEdge;
  nodeOps.forEach((el) => {
    if (!el) {
      return;
    }
    el.style.display = isNode ? '' : 'none';
  });
  // 新增状态在两种场景都显示
  if (els.ctxAddState) {
    els.ctxAddState.style.display = '';
  }
  // 新增事件入口取消（统一在“新增迁移”中自定义事件）
  if (els.ctxAddEvent) {
    els.ctxAddEvent.style.display = 'none';
  }
  // 边相关操作：仅在选中边时显示
  const edgeOps = [els.ctxEdgeRename, els.ctxEdgeReverse, els.ctxEdgeDelete];
  edgeOps.forEach((el) => {
    if (!el) {
      return;
    }
    el.style.display = isEdge ? '' : 'none';
  });
  // 渲染生命周期编辑内容
  if (els.ctxLife) {
    if (isNode) {
      const life = model.lifecycle[stateId] || {};
      if (els.ctxLifeEnter) {
        els.ctxLifeEnter.value = life.onEnter || '';
      }
      if (els.ctxLifeExit) {
        els.ctxLifeExit.value = life.onExit || '';
      }
      if (els.ctxLifeUpdate) {
        els.ctxLifeUpdate.value = life.onUpdate || '';
      }
    } else {
      if (els.ctxLifeEnter) {
        els.ctxLifeEnter.value = '';
      }
      if (els.ctxLifeExit) {
        els.ctxLifeExit.value = '';
      }
      if (els.ctxLifeUpdate) {
        els.ctxLifeUpdate.value = '';
      }
    }
  }
}

function generateTS() {
  // 生成 union 类型
  const sType = model.states.map((s) => `'${s}'`).join(' | ') || "'__empty'";
  const eType = model.events.map((e) => `'${e}'`).join(' | ') || "'__empty'";

  // 生成 on 映射：每个状态聚合其事件映射
  const onMap = {};
  model.states.forEach((s) => (onMap[s] = {}));
  model.transitions.forEach(({ from, event, to }) => {
    if (!onMap[from]) {
      onMap[from] = {};
    }
    onMap[from][event] = to;
  });

  function indent(lines, n) {
    const pad = ' '.repeat(n);
    return lines
      .split('\n')
      .map((l) => (l.trim() ? pad + l : l))
      .join('\n');
  }

  function lifeBlock(name, life) {
    const enter =
      (life && life.onEnter) || '(_, motion) => { /* TODO: onEnter */ }';
    const exit = (life && life.onExit) || '() => { /* TODO: onExit */ }';
    const update = (life && life.onUpdate) || '() => { /* TODO: onUpdate */ }';
    return [
      `onEnter: ${enter},`,
      `onExit: ${exit},`,
      `onUpdate: ${update},`,
    ].join('\n');
  }

  // 生成 states 配置
  const statesLines = model.states.map((s) => {
    const onEntries = Object.entries(onMap[s] || {})
      .map(([k, v]) => `${k}: '${v}'`)
      .join(', ');
    const onLine = onEntries ? `on: { ${onEntries} },` : '';
    const life = model.lifecycle[s] || {};
    const lifeLines = lifeBlock(s, life);
    const content = [onLine, lifeLines].filter(Boolean).join('\n');
    return `${s}: {${content ? `\n${indent(content, 6)}\n  ` : ''}},`;
  });

  const ts = `import { StateMachine, type MachineConfig } from '@dao3fun/fsm';

type S = ${sType};
type E = ${eType};

export const config: MachineConfig<S, E> = {
  initial: '${model.initial || ''}',
  states: {
    ${indent(statesLines.join('\n'), 4)}
  },
};
`;
  return ts;
}

function download(filename, text) {
  const a = document.createElement('a');
  a.setAttribute(
    'href',
    `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`
  );
  a.setAttribute('download', filename);
  a.style.display = 'none';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
}
